home *** CD-ROM | disk | FTP | other *** search
- /*
- * This module implements a simple access control language that is based on
- * host (or domain) names, netgroup, internet addresses (or network numbers)
- * and daemon process names. When a match is found an optional shell command
- * is executed and the search is terminated.
- *
- * Diagnostics are reported through syslog(3).
- *
- * Compile with -DNETGROUP if your library provides support for netgroups.
- *
- * Compile with -DUSER_AT_HOST for rule-driven username lookups.
- *
- * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
- */
-
- #ifndef lint
- static char sccsid[] = "@(#) hosts_access.c 1.10 93/03/07 22:47:36";
- #endif
-
- /* System libraries. */
-
- #include <sys/types.h>
- #include <sys/param.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <stdio.h>
- #include <syslog.h>
- #include <ctype.h>
- #include <errno.h>
-
- extern char *fgets();
- extern char *strchr();
- extern char *strtok();
-
- #ifndef INADDR_NONE
- #define INADDR_NONE (-1) /* XXX should be 0xffffffff */
- #endif
-
- /* Local stuff. */
-
- #include "log_tcp.h"
-
- #ifdef INET_ADDR_BUG
- #include "inet_addr_fix"
- #endif
-
- /* Delimiters for lists of daemons or clients. */
-
- static char sep[] = ", \t";
-
- /* Constants to be used in assignments only, not in comparisons... */
-
- #define YES 1
- #define NO 0
- #define FAIL (-1)
-
- /* Forward declarations. */
-
- static int table_match();
- static int list_match();
- static int client_match();
- static int string_match();
- static int masked_match();
- static char *xgets();
-
- /* The user@host access control. Trivial to add but complicates use. */
-
- #ifdef USER_AT_HOST
- static int userhost_match();
- #define CLIENT_MATCH userhost_match
- #else
- #define CLIENT_MATCH client_match
- #endif
-
- /* Size of logical line buffer. */
-
- #define BUFLEN 2048
-
- /* hosts_access - host access control facility */
-
- int hosts_access(daemon, client)
- char *daemon;
- struct from_host *client; /* host or user name may be empty */
- {
-
- /*
- * If the (daemon, client) pair is matched by an entry in the file
- * /etc/hosts.allow, access is granted. Otherwise, if the (daemon,
- * client) pair is matched by an entry in the file /etc/hosts.deny,
- * access is denied. Otherwise, access is granted. A non-existent
- * access-control file is treated as an empty file.
- */
-
- if (table_match(HOSTS_ALLOW, daemon, client))
- return (YES);
- if (table_match(HOSTS_DENY, daemon, client))
- return (NO);
- return (YES);
- }
-
- /* table_match - match table entries with (daemon, client) pair */
-
- static int table_match(table, daemon, client)
- char *table;
- char *daemon;
- struct from_host *client; /* host or user name may be empty */
- {
- FILE *fp;
- char sv_list[BUFLEN]; /* becomes list of daemons */
- char *cl_list; /* becomes list of clients */
- char *sh_cmd; /* becomes optional shell command */
- int match;
- int end;
-
- /* The following variables should always be tested together. */
-
- int sv_match = NO; /* daemon matched */
- int cl_match = NO; /* client matced */
-
- /*
- * Process the table one logical line at a time. Lines that begin with a
- * '#' character are ignored. Non-comment lines are broken at the ':'
- * character (we complain if there is none). The first field is matched
- * against the daemon process name (argv[0]), the second field against
- * the host name or address. A non-existing table is treated as if it
- * were an empty table. The search terminates at the first matching rule.
- * When a match is found an optional shell command is executed.
- */
-
- if (fp = fopen(table, "r")) {
- while (!(sv_match && cl_match) && xgets(sv_list, sizeof(sv_list), fp)) {
- if (sv_list[end = strlen(sv_list) - 1] != '\n') {
- syslog(LOG_ERR, "%s: missing newline or line too long", table);
- continue;
- }
- if (sv_list[0] == '#') /* skip comments */
- continue;
- while (end > 0 && isspace(sv_list[end - 1]))
- end--;
- sv_list[end] = '\0'; /* strip trailing whitespace */
- if (sv_list[0] == 0) /* skip blank lines */
- continue;
- if ((cl_list = strchr(sv_list, ':')) == 0) {
- syslog(LOG_ERR, "%s: malformed entry: \"%s\"", table, sv_list);
- continue;
- }
- *cl_list++ = '\0'; /* split 1st and 2nd fields */
- if ((sh_cmd = strchr(cl_list, ':')) != 0)
- *sh_cmd++ = '\0'; /* split 2nd and 3rd fields */
- if ((sv_match = list_match(sv_list, daemon, string_match)))
- cl_match = list_match(cl_list, (char *) client, CLIENT_MATCH);
- }
- (void) fclose(fp);
- } else if (errno != ENOENT) {
- syslog(LOG_ERR, "cannot open %s: %m", table);
- }
- match = (sv_match == YES && cl_match == YES);
- if (match && sh_cmd)
- OPTIONS_STYLE(sh_cmd, daemon, client);
- return (match);
- }
-
- /* list_match - match an item against a list of tokens with exceptions */
-
- static int list_match(list, item, match_fn)
- char *list;
- char *item;
- int (*match_fn) ();
- {
- char *tok;
- int match = NO;
-
- /*
- * Process tokens one at a time. We have exhausted all possible matches
- * when we reach an "EXCEPT" token or the end of the list. If we do find
- * a match, look for an "EXCEPT" list and recurse to determine whether
- * the match is affected by any exceptions.
- */
-
- for (tok = strtok(list, sep); tok != 0; tok = strtok((char *) 0, sep)) {
- if (strcasecmp(tok, "EXCEPT") == 0) /* EXCEPT: give up */
- break;
- if (match = (*match_fn) (tok, item)) /* YES or FAIL */
- break;
- }
- /* Process exceptions to YES or FAIL matches. */
-
- if (match != NO) {
- while ((tok = strtok((char *) 0, sep)) && strcasecmp(tok, "EXCEPT"))
- /* VOID */ ;
- if (tok == 0 || list_match((char *) 0, item, match_fn) == NO)
- return (match);
- }
- return (NO);
- }
-
- /* client_match - match host name and address against token */
-
- static int client_match(tok, item)
- char *tok;
- char *item;
- {
- struct from_host *client = (struct from_host *) item;
- int match;
-
- /*
- * Try to match the address first. If that fails, try to match the host
- * name if available.
- */
-
- if ((match = string_match(tok, client->addr)) == 0)
- if (client->name[0] != 0)
- match = string_match(tok, client->name);
- return (match);
- }
-
- #ifdef USER_AT_HOST
-
- /* userhost_match - do user@host access control */
-
- static int userhost_match(tok, item)
- char *tok;
- char *item;
- {
- struct from_host *client = (struct from_host *) item;
- int match = NO;
- char *at;
- int host_match;
- int user_match;
-
- /*
- * Warning: experimental code, enabled only when USER_AT_HOST is defined.
- *
- * Basically, you specify user_pattern@host_pattern where remote username
- * lookups are desired, and plain host_pattern for all other cases. The
- * syntax of user name patterns is the same as for hosts or daemons, but
- * ALL is probably the only user_pattern that makes sense.
- *
- * In case of UDP connections, the result of username lookup will always be
- * "unknown".
- *
- * Return FAIL if we match a pattern of the form user@FAIL or FAIL@host:
- * FAIL, like NO, is transitive. According to some people, such patterns
- * should be taken out and shot. Good news: FAIL is on its way out.
- */
-
- if (at = strchr(tok + 1, '@')) { /* user@host */
- *at = 0;
- if (host_match = client_match(at + 1, item)) {
- if (client->user[0] == 0) {
- if (client->sock_type != FROM_CONNECTED) {
- client->user = FROM_UNKNOWN;
- } else if (client->sin == 0) {
- syslog(LOG_ERR, "no socket info for user name lookup");
- client->user = FROM_UNKNOWN;
- } else {
- client->user = rfc931_name(client->sin);
- }
- }
- user_match = string_match(tok, client->user);
- if (user_match == NO || user_match == FAIL) {
- match = user_match;
- } else {
- match = host_match;
- }
- }
- *at = '@';
- } else { /* host */
- match = client_match(tok, item);
- }
- return (match);
- }
-
- #endif /* USER_AT_HOST */
-
- /* string_match - match string against token */
-
- static int string_match(tok, string)
- char *tok;
- char *string;
- {
- int tok_len;
- int str_len;
- char *cut;
- #ifdef NETGROUP
- static char *mydomain = 0;
- #endif
-
- /*
- * Return YES if a token has the magic value "ALL". Return FAIL if the
- * token is "FAIL". If the token starts with a "." (domain name), return
- * YES if it matches the last fields of the string. If the token has the
- * magic value "LOCAL", return YES if the string does not contain a "."
- * character. If the token ends on a "." (network number), return YES if
- * it matches the first fields of the string. If the token begins with a
- * "@" (netgroup name), return YES if the string is a (host) member of
- * the netgroup. Return YES if the token fully matches the string. If the
- * token is a netnumber/netmask pair, return YES if the address is a
- * member of the specified subnet.
- */
-
- if (tok[0] == '.') { /* domain: match last fields */
- if ((str_len = strlen(string)) > (tok_len = strlen(tok))
- && strcasecmp(tok, string + str_len - tok_len) == 0)
- return (YES);
- } else if (tok[0] == '@') { /* netgroup: look it up */
- #ifdef NETGROUP
- if (mydomain == 0)
- yp_get_default_domain(&mydomain);
- if (!isdigit(string[0])
- && innetgr(tok + 1, string, (char *) 0, mydomain))
- return (YES);
- #else
- syslog(LOG_ERR, "wrapper: netgroup support is not configured");
- return (NO);
- #endif
- } else if (strcasecmp(tok, "ALL") == 0) { /* all: match any */
- return (YES);
- } else if (strcasecmp(tok, "FAIL") == 0) { /* fail: match any */
- return (FAIL);
- } else if (strcasecmp(tok, "LOCAL") == 0) { /* local: no dots */
- if (strchr(string, '.') == 0 && strcasecmp(string, "unknown") != 0)
- return (YES);
- } else if (!strcasecmp(tok, string)) { /* match host name or address */
- return (YES);
- } else if (tok[(tok_len = strlen(tok)) - 1] == '.') { /* network */
- if (strncmp(tok, string, tok_len) == 0)
- return (YES);
- } else if ((cut = strchr(tok, '/')) != 0) { /* netnumber/netmask */
- if (isdigit(string[0]) && masked_match(tok, cut, string))
- return (YES);
- }
- return (NO);
- }
-
- /* masked_match - match address against netnumber/netmask */
-
- static int masked_match(tok, slash, string)
- char *tok;
- char *slash;
- char *string;
- {
- unsigned long net;
- unsigned long mask;
- unsigned long addr;
-
- if ((addr = inet_addr(string)) == INADDR_NONE)
- return (NO);
- *slash = 0;
- net = inet_addr(tok);
- *slash = '/';
- if (net == INADDR_NONE || (mask = inet_addr(slash + 1)) == INADDR_NONE) {
- syslog(LOG_ERR, "bad net/mask access control: %s", tok);
- return (NO);
- }
- return ((addr & mask) == net);
- }
-
- /* xgets - fgets() with backslash-newline stripping */
-
- static char *xgets(buf, len, fp)
- char *buf;
- int len;
- FILE *fp;
- {
- int got;
- char *start = buf;
-
- for (;;) {
- if (fgets(buf, len, fp) == 0)
- return (buf > start ? start : 0);
- got = strlen(buf);
- if (got >= 2 && buf[got - 2] == '\\' && buf[got - 1] == '\n') {
- got -= 2;
- buf += got;
- len -= got;
- buf[0] = 0;
- } else {
- return (start);
- }
- }
- }
-